<?php

abstract class E_ARB {
//http://www.hiteshagrawal.com/php/oops-in-php5/oops-in-php5-tutorial-abstract-class
//http://www.hiteshagrawal.com/php/oops-in-php5/oops-in-php5-static-keyword
//http://www.phpro.org/tutorials/Introduction-to-PHP-PDO.html

	private static $dbh = false;

	protected	$table_name,
				$validates_presence_of,
				$belongs_to,
				$has_many,
				$has_one,
				$has_and_belongs_to_many,
				$class_name,
				$fkey;



// --------------------------------------------magic methods begin------------------------------------------------------------------------------

static function get_static($class_name, $static_var) {
	//call_user_func(array('Foo', 'bar') [, $params] );
	eval('$foo = ' . $class_name . '::$'.$static_var.';');
	return $foo;
}

static function set_static($class_name, $static_var, $val) {
	eval($class_name . '::$' . $static_var . ' = "' . $val . '";');
}


	function E_ARB($constructor_action_or_params = null, $conditions = null) {
		//aquire db conn
		if (!self::$dbh) { self::$dbh = Database_connections::get_connection(); }

		$this->class_name = get_class($this);
		$lowercaseclassname = strtolower($this->class_name);
		$this->fkey = $lowercaseclassname.'_id';

		if (!isset($this->table_name)) {$this->table_name = $lowercaseclassname.'s';}
		if (method_exists($this, 'initialize')) { $this->initialize($constructor_action_or_params);	}
		if (isset($constructor_action_or_params)) {
			if (is_array($constructor_action_or_params)) { $this->set_attributes($constructor_action_or_params); return $this; }
			else { return $this->$constructor_action_or_params($conditions); }
		}
	}


	public function __get($dt) { return $this->getData($dt); }

	function __set($name, $vl) {
		if (isset_in_array($name, $this->belongs_to)) {
			$fkey = $vl->fkey;
			$this->$fkey = $vl->id;
		}
		else { $this->$name = $vl; }
	}

	public function __toString() { return $this->id; }

/*


	function __destruct() {}
	function __set($dt, $vl) {}

	function __call($function, $args) {
		echo 'a is smaller than b';
		print 'Call to $function() with args '$args' failed!\n';
	}
 
	function __isset($variable)	{}
 	function __callStatic() {}	//google for info
 	function __unset() {} 		//google for info
 	function __sleep() {} 		//google for info
 	function __wakeup() {} 		//google for info
 	function __set_state() {} 	//google for info
 	function __clone() {} 		//google for info
*/




// --------------------------------------------magic methods end--------------------------------------------------------------------------------


// --------------------------------------------relational methods begin--------------------------------------------------------------------------

	private function getData($dt) {
//		echo $this->class_name, '<br />';
//		echo get_called_class();
		$class_n = ucfirst($dt);
		if (substr($dt, -1) == 's') { //has many or has and belongs to many or tring to get many has ones ie users->customers || customers->users
			$class_n = substr($class_n, 0, -1);
			if (isset_in_array($dt, $this->has_many)) {	return $class_n::find('all', array('conditions' => "{$this->fkey} = {$this->id}")); }
			elseif (isset_in_array($dt, $this->has_and_belongs_to_many)) {
				$many_class_forign_key = strtolower($class_n) . '_id';
				$table_array = array($this->table_name, $dt);
				sort($table_array);
				$has_and_belongs_to_many_table_name = implode('_', $table_array);

				return $class_n::find('all', array(
							'joins' => array("{$has_and_belongs_to_many_table_name} AS hbm" => "hbm.{$many_class_forign_key} = {$dt}.id"),
							'conditions' => "hbm.{$this->fkey} = {$this->id}"
							));
				/*
				//query using where
				$sth = self::$dbh->prepare("SELECT $dt.*
												FROM $has_and_belongs_to_many_table_name, $dt
												WHERE $dt.id = $has_and_belongs_to_many_table_name.$many_class_forign_key
												AND $has_and_belongs_to_many_table_name.$this_class_forign_key = :id");
				//query using inner join
				$sth = self::$dbh->prepare("SELECT $dt.*
												FROM $dt INNER JOIN $has_and_belongs_to_many_table_name
												ON $dt.id = $has_and_belongs_to_many_table_name.$many_class_forign_key
												WHERE $has_and_belongs_to_many_table_name.$this_class_forign_key = :id");
				$sth->bindParam(':id', $this->id);
				*/
			}
		}
		elseif (isset_in_array($dt, $this->has_one)) { return $class_n::find('first', array('conditions' => "{$this->fkey} = {$this->id}")); }
		elseif (isset_in_array($dt, $this->belongs_to)) {
			$key_name = strtolower($dt) . '_id';
			return $class_n::find($this->$key_name);
		}
		else { return false; } //echo "<br />$dt has not get been set for this object<br />";
	}


// --------------------------------------------relational methods end----------------------------------------------------------------------------





// --------------------------------------------save to db methods begin--------------------------------------------------------------------------

	//single << (i think)
	function append_to_has_one($non_saved_obj) { return $this->append_to_many($non_saved_obj); }

	function append_list_to_many($arrayListOfItems) {
		foreach ($arrayListOfItems as $item) { if (!$this->append_to_many($item)) { return false; }	}
		return true; //???
	}

	function append_to_many($non_saved_obj) { //single << (i think)
		//notiece im NOT saing $product->comments->append_to_many($comment); just $product->append_to_many($comment);..i think usage comment added later
		$fkey = $this->fkey;
		$non_saved_obj->$fkey = $this->id;
		return $non_saved_obj->save();
	}


	function append_many_to_many($has_many_obj) { //single << (i think)
		//has and belong to many appending
		$table_name = array($this->table_name, $has_many_obj->table_name);
		sort($table_name);
		$table_name = implode('_', $table_name);

		$sth = self::$dbh->prepare("INSERT INTO {$table_name} ({$this->fkey},{$has_many_obj->fkey}) VALUES (:thisid, :fkey)");
		$sth->bindParam(':thisid', $this->id);
		$sth->bindParam(':fkey', $has_many_obj->id);
		
		return $sth->execute();
	}

	function remove_many_to_many($to_remove_obj) {
		//for removing entereis in has_and _belongs_to_many table

		$hbm_variable_name = $to_remove_obj->table_name;
		if (in_array($to_remove_obj, $this->$hbm_variable_name)) {

			$table_name = array($this->table_name, $to_remove_obj->table_name);
			sort($table_name);
			$table_name = implode('_', $table_name);

			$sth = self::$dbh->prepare("DELETE FROM {$table_name} WHERE {$this->fkey} = :thisid AND {$to_remove_obj->fkey} = :toremid");
			$sth->bindParam(':thisid', $this->id);
			$sth->bindParam(':toremid', $to_remove_obj->id);

			return $sth->execute();
		}
		return false;
	}


	function save() {
		if (isset($this->id)) { return $this->id; }
		return $this->run_create_or_update();
	}

	//NOT COMPLETED!im wondering if i should remove the $create_or_update param and make the presence of id determeine what operatin is requested
	function run_create_or_update($new_params = null) {
		if ($this->application_validation($new_params)) {
			if ($this->application_db_interaction($new_params)) { return true; }
			echo "{$create_or_update} failed";
			return false;
		}
		echo "validation for {$create_or_update} failed!";
		return false;
	}

	function update_or_create_function($update_params = null) {
		$obj_array = get_public_object_vars($this);

		if (!isset($update_params)) {
			$col_names_array = array_keys($obj_array);
			$values_array = array_values($obj_array);
			$col_names = implode(', ', $col_names_array);
			$values = implode("', '", $values_array);
			$sql = "INSERT INTO {$this->table_name} ({$col_names}) VALUES ('{$values}')";
			//echo "<br />$sql<br />";
			if (!$this->excute_sql($sql)) {	echo 'create sql excution failed!'; return false; }
			$this->id = self::$dbh->lastInsertId();
		}
		else {
			$result = array_diff_assoc($update_params, $obj_array);
			if (!empty($result)) {
				$upd_arry = array();
				while (list($key, $value) = each($result)) { array_push($upd_arry, "{$key} = '{$value}'"); }
				$upd_arry = implode(', ', $upd_arry);
				$sql = "UPDATE {$this->table_name} SET {$upd_arry} WHERE id = {$this->id}";
				if (!$this->excute_sql($sql)) { echo ' UPDATE sql excution failed! '; return false;	}
			}
		}
		return $this->id;
	}

	function update($new_params) { return $this->run_create_or_update($new_params); }

	function application_before_validation() {return true;}
	function application_after_validation() {return true;}
	function application_before_save() {return true;}
	function application_after_save() {return true;}

	function application_validation($update_params) {
		if (!$this->application_before_validation()) { return false;}
		if (method_exists($this,'before_validation') && (!$this->before_validation())) {return false;}
		if (!$update_params) { $before_validation_on = 'before_validation_on_create'; $after_validation_on = 'after_validation_on_create'; }
		else { $before_validation_on = 'before_validation_on_update'; $after_validation_on = 'after_validation_on_update'; }
		if (method_exists($this,$before_validation_on) && (!$this->$before_validation_on())) {return false;}
		if (!$this->application_validation_function()) {return false;}
		if (!$this->application_after_validation()) {return false;}
		if (method_exists($this,'after_validation') && (!$this->after_validation())) {return false;}
		if (method_exists($this,$after_validation_on) && (!$this->$after_validation_on())) {return false;}
		
		return true;
	}

	function application_validation_function() {
		if (isset($this->validates_presence_of) && is_array($this->validates_presence_of)) { foreach($this->validates_presence_of as $checking_for_attribute) { if (($this->$checking_for_attribute == '') || (!isset($this->$checking_for_attribute))) { return false; } } }
		return true;
	}

	function application_db_interaction($update_params = null) {
		if (!$this->application_before_save()) {return false;}
		if (method_exists($this,'before_save') && (!$this->before_save())) {return false;}
		if (!isset($update_params)) { $before_interaction = 'before_create'; $after_interaction = 'after_create'; }
		else { $before_interaction = 'before_update';	$after_interaction = 'after_update'; }
		if (method_exists($this,$before_interaction) && (!$this->$before_interaction())) {return false;}
		if (!$this->update_or_create_function($update_params)) {return false;}
		//doesnt make sence returning false save already occured
		if (!$this->application_after_save()) {}
		if (method_exists($this,$after_interaction) && (!$this->$after_interaction())) {}
		if (method_exists($this,'after_save') && (!$this->after_save())) {}
		
		return $this->id;
	}
	

	function destroy() {
		$sql = "DELETE FROM {$this->table_name} WHERE id = {$this->id}";
			
		$sth = self::$dbh->prepare($sql);
		if ($sth->execute()) {
			if (is_array($this->has_many)) {
				foreach ($this->has_many as $key => $val) {
					//if ($key == 'on_delete_remove') { $sql = "DELETE FROM {$table_name} WHERE {$this->fkey}='{$this->id}'"; $this->excute_sql($sql); }
 					if ($val == 'on_delete_remove') { $sql = "DELETE FROM {$key} WHERE {$this->fkey}='{$this->id}'"; $this->excute_sql($sql); }
				}
			}
			if (is_array($this->has_and_belongs_to_many)) {
				foreach ($this->has_and_belongs_to_many as $hbm) {
					$table_name = array($this->table_name, $hbm);
					sort($table_name);
					$table_name = implode('_', $table_name);
					$sql = "DELETE FROM {$table_name} WHERE {$this->fkey}='{$this->id}'";
					return $this->excute_sql($sql);
				}
			}
			return true; //no has and belongs to many relations
		}
		return false;
	}

	function after_destroy() {}

// --------------------------------------------save to db methods end----------------------------------------------------------------------------




// --------------------------------------------find from db methods begin------------------------------------------------------------------------





/*
		//works
		$this->products = Product::find('all', 'Product', array(
							'add_from' => array('playgroundequipments_products AS pp', 'playgroundequipments AS pe'),
							'conditions' => "pp.product_id = products.id 
											AND pp.playgroundequipment_id = pe.id 
											AND pe.name = '{$_GET['type']}'",
							'order' => $_SESSION['sort']
							));

		//works
		$this->products = Product::find('all', 'Product', array(
					'joins' => array(
						'playgroundequipments_products pp' => 'pp.product_id = products.id',
						'playgroundequipments pe' => 'pp.playgroundequipment_id = pe.id'
						),
					'conditions' => array("pe.name = ?", $_GET['type']),
					'order' => $_SESSION['sort']
					));

		//works
		$this->products = Product::find('all', 'Product', array('conditions' => "products.id IN (
							SELECT playgroundequipments_products.product_id
							FROM playgroundequipments_products
							WHERE playgroundequipments_products.playgroundequipment_id IN (
									SELECT playgroundequipments.id
									FROM playgroundequipments
									WHERE playgroundequipments.name = '{$_GET['type']}'
								)
							)", 'order' => $_SESSION['sort']));

*/




//find('all', array('order' => 'id DESC', 'conditions" => "status_id = '3' AND date_fixed > {$start_date} AND date_fixed <= {$end_date} AND product_id = {$id}", 'offset' => 10, 'limit' => 10, 'include' => array('account', 'friends')))

/*
to get the last row of a table, I found I could do it like this: -

Code:

select * from articles order by artid desc limit 1;

It basically lists all the rows in decreasing artid order, and only select the first 1 (which is the last row in the table as it's reversed). To get the second from last: -

Code:

select * from articles order by artid desc limit 1,1;

when limit is given two params, it uses the first number as an offset, and the second number as the number of rows to get. If you wanted you could get all last 8 rows by saying .....limit 0,8.
*/

static function find($selection = 'all', $conditions_array = array()) {
// 	echo '<p>';
// 	print_r(debug_backtrace());
// 	echo '</p>';
	//if (method_exists($class_name,'initialize')) { call_user_func(array($class_name,'initialize')); }
	$class_name = get_called_class();
	$generated_sth_res = self::generate_sql_prepare($selection, $class_name, $conditions_array);
	$sth = $generated_sth_res['sth'];
	$conditions_array = $generated_sth_res['conditions_array'];

	if ($sth->execute()) {
		if ($selection == 'all' || is_array($selection)) { return $sth->fetchALL(PDO::FETCH_CLASS, $class_name); }
		return $sth->fetchObject($class_name);
	}
	return false;
}



static function generate_sql_prepare($selection = 'all', $class_name, $conditions_array = array()) {
	$needtofindtablename = new $class_name;
	$table_name = $needtofindtablename->table_name;
	unset($needtofindtablename);
		

	if (is_int($selection)) { $sql = 'SELECT ' . $table_name . '.* FROM ' . $table_name . ' WHERE id = ' . $selection; }
	elseif (is_array($selection)) {
		$id_list = array_map('intval', $selection);
		$id_list = implode(',', $id_list);
		$sql = 'SELECT ' . $table_name . '.* FROM ' . $table_name . ' WHERE id IN (' . $id_list . ')';
	}
	else {
		$sql = 'SELECT';

		if (isset($conditions_array['select'])) { $sql .= " {$conditions_array['select']}"; }
		else { $sql .= " $table_name.*"; }

		if (isset($conditions_array['from'])) {
			if (is_array($conditions_array['from'])) { $sql .= ' FROM ' . implode(',', $conditions_array['from']); }
			else { $sql .= " FROM {$conditions_array['from']}"; }
		}
		else { $sql .= " FROM {$table_name}"; }

		if (isset($conditions_array['add_from']) && is_array($conditions_array['add_from'])) {
			foreach($conditions_array['add_from'] as $from_table) { $sql .= ", $from_table"; }
		}

		if (isset($conditions_array['joins']) && is_array($conditions_array['joins'])) {
			foreach($conditions_array['joins'] as $to_join_table_name => $on_sql) { $sql .= " INNER JOIN $to_join_table_name ON $on_sql"; }
		}

		if (isset($conditions_array['conditions'])) {
			if (is_array($conditions_array['conditions'])) { $sql .= " WHERE {$conditions_array['conditions'][0]}"; unset($conditions_array['conditions'][0]); }
			else { $sql .= " WHERE {$conditions_array['conditions']}"; }
		}

		switch ($selection) { 
			case 'first':
				$sql .= ' ORDER BY';
				$sql .= (isset($conditions_array['order'])) ? " {$conditions_array['order']}" : ' id ASC';
				$sql .=  ' LIMIT 1';
				if (isset($conditions_array['offset'])) { $sql .= " OFFSET {$conditions_array['offset']}"; }
				
			break;
			case 'last':
				$sql .= ' ORDER BY';
				$sql .= (isset($conditions_array['order'])) ? " {$conditions_array['order']}" : ' id DESC';
				$sql .=  ' LIMIT 1';
				if (isset($conditions_array['offset'])) { $sql .= " OFFSET {$conditions_array['offset']}"; }
				
			break;
			case 'all':
				//if (isset($conditions_array['order'])) { $sql .= " ORDER BY {$conditions_array['order']}"; }
				$sql .= ' ORDER BY';
				$sql .= (isset($conditions_array['order'])) ? " {$conditions_array['order']}" : ' id ASC';
				if (isset($conditions_array['limit'])) { $sql .= " LIMIT {$conditions_array['limit']}"; }
				if (isset($conditions_array['offset'])) { $sql .= " OFFSET {$conditions_array['offset']}"; }
				
			break;
			default:
				//im goin to pretend tha this will be a number
				$selection2 = intval($selection);
				$sql = "SELECT * FROM {$table_name} WHERE id = {$selection2}";
				//echo "{$class_name} :: default clause selection in find " . var_dump($selection) . '<br />';
			break;
			
		}
		
	}
 	//echo "$sql <br />";
	$sth = self::$dbh->prepare($sql);
	if (isset($conditions_array['conditions']) && is_array($conditions_array['conditions'])) {
		foreach($conditions_array['conditions'] as $key => $bindingParam) { $sth->bindParam($key, $bindingParam); }
	}

	return array('sth' => $sth, 'conditions_array' => $conditions_array);
}



static function count_records($selection = 'all', $class_name, $conditions_array = array()) {
	$conditions_array['select'] = 'count(*)';

	$generated_sth_res = self::generate_sql_prepare($selection, $class_name, $conditions_array);
	$sth = $generated_sth_res['sth'];
	$conditions_array = $generated_sth_res['conditions_array'];

	if ($sth->execute()) {
		$res = $sth->fetch(PDO::FETCH_ASSOC);
		return $res[$conditions_array['select']];
	}
	return false;
}

static function paginate($selection = 'all', $class_name, $conditions_array = array()) {
	$page = $conditions_array['page'];
	unset($conditions_array['page']);
	$items_per_page = $conditions_array['per_page'];
	unset($conditions_array['per_page']);
	$conditions_array['limit'] = $items_per_page;
	$conditions_array['offset'] = ($page - 1) * $items_per_page;
	return $class_name::find($selection = 'all', $conditions_array);
}


// --------------------------------------------find from db methods end--------------------------------------------------------------------------



// --------------------------------------------other class methods begin-------------------------------------------------------------------------


	function set_attributes($params) { if (is_array($params)) { while (list($key, $val) = each($params)) { $this->$key = $val; } } }


	static function excute_sql($sql) {
		//echo $sql;
		//db connection always aquired when applicaion record is included or called (see top of file)
		$sth = self::$dbh->prepare($sql);
		if ($sth->execute()) { return $sth;	}
		return false;
	}

// --------------------------------------------other class methods end---------------------------------------------------------------------------


}



?>